package com.yunpeng.dishes.biz.services.impl;

import com.alibaba.fastjson.JSONObject;
import com.yunpeng.common.exception.BizException;
import com.yunpeng.dishes.biz.services.IDishesInternalService;
import com.yunpeng.dishes.biz.services.IDishesNutrientsService;
import com.yunpeng.dishes.biz.services.IFoodNutrientsService;
import com.yunpeng.dishes.biz.utils.TransformUtil;
import com.yunpeng.dishes.common.constants.ConstantValues;
import com.yunpeng.dishes.common.dtos.business.DishesFoodSaveDTO;
import com.yunpeng.dishes.common.dtos.business.DishesSaveDTO;
import com.yunpeng.dishes.common.dtos.business.OrderDishesSnapshotDTO;
import com.yunpeng.dishes.common.enums.ResultCodeEnum;
import com.yunpeng.dishes.common.vo.*;
import com.yunpeng.dishes.dao.mapper.entity.ICommonMapper;
import com.yunpeng.dishes.dao.mapper.entity.IDishesMapper;
import com.yunpeng.dishes.dao.mapper.entity.IFoodIngredientsMapper;
import com.yunpeng.dishes.dao.mapper.entity.IMerchantDishesMapper;
import com.yunpeng.dishes.dao.mapper.relation.IDishesFoodIngredientsMapper;
import com.yunpeng.dishes.dao.mapper.relation.IDishesNutrientsMapper;
import com.yunpeng.dishes.dao.mapper.relation.IOrderDishesNutrientsMapper;
import com.yunpeng.dishes.dao.model.entity.DishesDO;
import com.yunpeng.dishes.dao.model.entity.FoodIngredientsDO;
import com.yunpeng.dishes.dao.model.entity.MerchantDishesDO;
import com.yunpeng.dishes.dao.model.relation.DishesFoodIngredientsDO;
import com.yunpeng.dishes.dao.model.relation.DishesNutrientsDO;
import com.yunpeng.dishes.dao.model.relation.OrderDishesNutrientsDO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * DishesNutrientsServiceImpl
 *
 * @author : Moore(hemw)
 * @date : 2020-03-26
 */
@Service
public class DishesNutrientsServiceImpl implements IDishesNutrientsService {

    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(DishesNutrientsServiceImpl.class);

    /**
     * dishesCode锁对象
     */
    private static final Object T_CODE_LOCK = new Object();
    /**
     * dishesCode锁在途数量
     */
    private static Integer T_CODE_HOLD_COUNT = 0;
    /**
     * merchantDishesCode锁对象
     */
    private static final Object M_CODE_LOCK = new Object();
    /**
     * merchantDishesCode锁在途数量
     */
    private static Integer M_CODE_HOLD_COUNT = 0;

    /**
     * 菜品mapper
     */
    @Resource
    private IDishesMapper dishesMapper;

    /**
     * 商户菜品mapper
     */
    @Resource
    private IMerchantDishesMapper merchantDishesMapper;

    /**
     * 菜品食材映射mapper
     */
    @Resource
    private IDishesFoodIngredientsMapper dishesFoodIngredientsMapper;

    @Resource
    /**
     * 食材服务
     */
    private IFoodNutrientsService foodNutrientsService;

    @Resource
    /**
     * 食材mapper
     */
    private IFoodIngredientsMapper foodIngredientsMapper;

    @Resource
    /**
     * 菜品营养元素mapper
     */
    private IDishesNutrientsMapper dishesNutrientsMapper;

    @Resource
    /**
     * 订单菜品营养元素快照
     */
    private IOrderDishesNutrientsMapper orderDishesNutrientsMapper;

    @Resource
    private IDishesInternalService dishesInternalService;

    @Resource
    private ICommonMapper commonMapper;

    @Override
    /**
     * 获取指定菜品菜品营养表数据
     * @param code
     * @return
     */
    public DishesNutrientsVO queryNutrientsOfDishesByCode(String code) {
        LOGGER.info("function start parameter: {}.", code);
        MerchantDishesDO dishesDO = merchantDishesMapper.getByCode(code);
        if (dishesDO == null) {
            LOGGER.info("function execute return null parameter: {}.", code);
            return null;
        }
        return queryNutrientsOfDishesByDishes(dishesDO);
    }

    @Override
    /**
     * 获取指定菜品菜品营养表数据
     * @param relationId
     * @return
     */
    public DishesNutrientsVO queryNutrientsOfDishesById(Long relationId) {
        LOGGER.info("function start parameter: {}.", relationId);
        MerchantDishesDO dishesDO = merchantDishesMapper.getByRelationId(relationId);
        if (dishesDO == null) {
            LOGGER.info("function execute return null parameter: {}.", relationId);
            return null;
        }
        return queryNutrientsOfDishesByDishes(dishesDO);
    }

    /**
     * 根据名称获取10条（最多）参考菜品营养成分表
     *
     * @param name
     * @return
     */
    @Override
    public List<DishesNutrientsVO> queryReferenceDishesListByName(String name) {
        LOGGER.info("function start parameter: {}.", name);
        List<DishesDO> dishesDOList = dishesMapper.queryForListByName(name);
        if (dishesDOList == null || dishesDOList.isEmpty()) {
            LOGGER.info("function execute return null parameter: {}.", name);
            return null;
        }
        //获取菜品代码列表
        List<String> dishesCodes = new ArrayList<>();
        for (DishesDO dishesDO : dishesDOList) {
            dishesCodes.add(dishesDO.getCode());
        }
        List<DishesFoodIngredientsDO> dishesFoodIngredientsDOList = dishesFoodIngredientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistFoods = (dishesFoodIngredientsDOList == null || dishesFoodIngredientsDOList.isEmpty());
        List<DishesNutrientsDO> dishesNutrientsDOList = dishesNutrientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistsNutrientsRecord = (dishesNutrientsDOList == null || dishesNutrientsDOList.isEmpty());
        //无食材列表、营养成分表直接返回
        if (notExistFoods && notExistsNutrientsRecord) {
            LOGGER.info("function execute return dishesDoList,null,null parameter: {}.", name);
            return dishesDOList.stream().map(x -> TransformUtil.transform(x, null, null)).collect(Collectors.toList());
        }
        //处理食材列表
        Map<String, List<DishesFoodVO>> dishesFoodVOListMap = getDishesFoodVoMap(dishesFoodIngredientsDOList, notExistFoods);
        //处理菜品营养元素列表
        Map<String, List<NutrientsVO>> dishesNutrientsVOListMap = getNutrientsVoMap(dishesNutrientsDOList, notExistsNutrientsRecord);
        return dishesDOList.stream().map(x -> TransformUtil.transform(x, dishesFoodVOListMap.get(x.getCode()), dishesNutrientsVOListMap.get(x.getCode()))).collect(Collectors.toList());
    }


    @Override
    /**
     * 保存商户菜品数据
     * @param dishesSaveDTO
     * @return
     */
    public Boolean saveDishesFoodRelation(DishesSaveDTO dishesSaveDTO) {
        LOGGER.info("function start parameter: {}.", dishesSaveDTO);
        MerchantDishesDO dishesDO = merchantDishesMapper.getByRelationId(dishesSaveDTO.getMerchantDishesId());
        boolean isNew = false;
        if (dishesDO == null) {
            dishesDO = new MerchantDishesDO();
            //初始化基础字段
            dishesDO.initForNew(dishesSaveDTO.getUserId());
            dishesDO.setName(dishesSaveDTO.getName());
            dishesDO.setCode(generateMerchantDishesCode());
            dishesDO.setMerchantId(dishesSaveDTO.getMerchantId());
            dishesDO.setMerchantDishesId(dishesSaveDTO.getMerchantDishesId());
            dishesDO.setWeight(dishesSaveDTO.getWeight());
            isNew = true;
        } else {
            dishesDO.setUpdater(dishesSaveDTO.getUserId());
            dishesDO.setUpdateTime(new Date());
            dishesDO.setName(dishesSaveDTO.getName());
            dishesDO.setMerchantId(dishesSaveDTO.getMerchantId());
            dishesDO.setMerchantDishesId(dishesSaveDTO.getMerchantDishesId());
            dishesDO.setWeight(dishesSaveDTO.getWeight());
        }
        List<String> foodCodeList = new ArrayList<>();
        Map<String, DishesFoodSaveDTO> dishesFoodSaveDtoMap = new HashMap<>(16);
        //处理关联食材
        for (DishesFoodSaveDTO dishesFoodSaveDTO : dishesSaveDTO.getDishesFoodList()) {
            foodCodeList.add(dishesFoodSaveDTO.getCode());
            dishesFoodSaveDtoMap.put(dishesFoodSaveDTO.getCode(), dishesFoodSaveDTO);
        }
        List<FoodIngredientsDO> foodIngredientsDOList = foodIngredientsMapper.queryForListByCodes(foodCodeList);
        List<FoodNutrientsVO> foodNutrientsVOList = foodNutrientsService.queryFoodNutrientsByFoods(foodIngredientsDOList);
        List<DishesFoodIngredientsDO> waitSaveDishesFoodList = new ArrayList<>();
        Map<String, DishesNutrientsDO> waitSaveDishesNutrientsList = new HashMap<>(16);
        if (foodNutrientsVOList != null && !foodNutrientsVOList.isEmpty()) {
            //处理菜品食材关联
            for (FoodNutrientsVO foodNutrientsVO : foodNutrientsVOList) {
                DishesFoodSaveDTO dishesFoodSaveDto = dishesFoodSaveDtoMap.get(foodNutrientsVO.getCode());
                //组装存入数据库的菜品食材关系
                DishesFoodIngredientsDO dishesFoodIngredientsDO = TransformUtil.transform(dishesDO, dishesFoodSaveDto, foodNutrientsVO);
                waitSaveDishesFoodList.add(dishesFoodIngredientsDO);
                //处理菜品营养成分表
                List<NutrientsVO> nutrients = foodNutrientsVO.getNutrients();
                if (nutrients != null && !nutrients.isEmpty()) {
                    for (NutrientsVO nutrient : nutrients) {
                        DishesNutrientsDO dishesNutrientsDO = TransformUtil.transform(dishesDO, dishesFoodIngredientsDO, waitSaveDishesNutrientsList, nutrient);
                        waitSaveDishesNutrientsList.put(nutrient.getCode(), dishesNutrientsDO);
                    }
                }
            }
        }
        List<DishesNutrientsDO> dishesNutrientsDOList = new ArrayList<>(waitSaveDishesNutrientsList.values());
        //保存菜品数据
        try {
            dishesInternalService.saveMerchantDishes(dishesDO, waitSaveDishesFoodList, dishesNutrientsDOList);
        } catch (Exception ex) {
            throw new BizException(ResultCodeEnum.SAVE_DATA_FAILED);
        } finally {
            if (isNew) {
                synchronized (M_CODE_LOCK) {
                    M_CODE_HOLD_COUNT--;
                }
            }
        }
        LOGGER.info("function end parameter: {}.", dishesSaveDTO);
        return true;
    }

    @Override
    /**
     * 转成菜品模板
     * @param code
     * @param user
     */
    public void copyToDishesTemplate(String code, Long user) {
        LOGGER.info("function start parameter:{},{}.", code, user);
        MerchantDishesDO merchantDishesDO = merchantDishesMapper.getByCode(code);
        if (merchantDishesDO == null) {
            throw new BizException(ResultCodeEnum.DATA_ACCESS_FAIL);
        }
        DishesDO dishesDO = TransformUtil.transform(user, generateDishesCode(), merchantDishesDO);

        List<String> dishesCodes = new ArrayList<String>() {{
            add(merchantDishesDO.getCode());
        }};
        List<DishesFoodIngredientsDO> dishesFoodIngredientsDOList = dishesFoodIngredientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistFoods = (dishesFoodIngredientsDOList == null || dishesFoodIngredientsDOList.isEmpty());
        List<DishesNutrientsDO> dishesNutrientsDOList = dishesNutrientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistsNutrientsRecord = (dishesNutrientsDOList == null || dishesNutrientsDOList.isEmpty());
        if (notExistFoods && notExistsNutrientsRecord) {
            try {
                dishesMapper.add(dishesDO);
            } catch (Exception ex) {
                throw new BizException(ResultCodeEnum.SAVE_DATA_FAILED);
            } finally {
                synchronized (T_CODE_LOCK) {
                    T_CODE_HOLD_COUNT--;
                }
            }
            LOGGER.info("function end with dishedDO,null,null parameter:{},{}.", code, user);
            return;
        }
        if (!notExistFoods) {
            for (DishesFoodIngredientsDO dishesFoodIngredientsDO : dishesFoodIngredientsDOList) {
                dishesFoodIngredientsDO.setDishesCode(dishesDO.getCode());
            }
        }
        if (!notExistsNutrientsRecord) {
            for (DishesNutrientsDO dishesNutrientsDO : dishesNutrientsDOList) {
                dishesNutrientsDO.setDishesCode(dishesDO.getCode());
            }
        }
        try {
            dishesInternalService.copyToDishesTemplate(dishesDO, dishesFoodIngredientsDOList, dishesNutrientsDOList);
        } catch (Exception ex) {
            throw new BizException(ResultCodeEnum.SAVE_DATA_FAILED);
        } finally {
            synchronized (T_CODE_LOCK) {
                T_CODE_HOLD_COUNT--;
            }
        }
        LOGGER.info("function end parameter:{},{}.", code, user);
    }

    @Override
    /**
     * 保存订单菜品营养成分表快照
     * @param orderDishesSnapshotDto
     */
    public void saveOrderNutrientsSnapshot(OrderDishesSnapshotDTO orderDishesSnapshotDto) {
        LOGGER.info("function start parameter:{}.", orderDishesSnapshotDto);
        List<MerchantDishesDO> dishesDOList = merchantDishesMapper.queryForListByRelationIds(orderDishesSnapshotDto.getDishesIds());
        if (dishesDOList == null || dishesDOList.isEmpty()) {
            OrderDishesNutrientsDO orderDishesNutrientsDO = TransformUtil.transform(orderDishesSnapshotDto, null);
            orderDishesNutrientsMapper.add(orderDishesNutrientsDO);
            LOGGER.info("function end with non snapshot-1 parameter:{}.", orderDishesSnapshotDto);
            return;
        }
        List<DishesNutrientsVO> dishesNutrientsVOList = getDishesNutrientsList(dishesDOList);
        if (dishesNutrientsVOList == null) {
            OrderDishesNutrientsDO orderDishesNutrientsDO = TransformUtil.transform(orderDishesSnapshotDto, null);
            orderDishesNutrientsMapper.add(orderDishesNutrientsDO);
            LOGGER.info("function end with non snapshot-2 parameter:{}.", orderDishesSnapshotDto);
            return;
        }
        JSONObject snapshot = new JSONObject();
        Map<String, NutrientsVO> nutrientsVoMap = new HashMap<>(16);
        for (DishesNutrientsVO dishesNutrientsVO : dishesNutrientsVOList) {
            if (dishesNutrientsVO.getNutrients() != null && !dishesNutrientsVO.getNutrients().isEmpty()) {
                //统计订单维度结果
                for (NutrientsVO nutrient : dishesNutrientsVO.getNutrients()) {
                    if (nutrientsVoMap.containsKey(nutrient.getCode())) {
                        NutrientsVO existNutrientsVO = nutrientsVoMap.get(nutrient.getCode());
                        nutrient.setNetContent(existNutrientsVO.getNetContent().add(nutrient.getNetContent()).setScale(2, RoundingMode.HALF_DOWN));
                    }
                    nutrientsVoMap.put(nutrient.getCode(), nutrient);
                }
            }
        }
        snapshot.put("dishes", dishesNutrientsVOList);
        snapshot.put("nutrients", JSONObject.toJSON(nutrientsVoMap.values()));
        OrderDishesNutrientsDO orderDishesNutrientsDO = TransformUtil.transform(orderDishesSnapshotDto, snapshot);
        OrderDishesNutrientsDO existsSnapShot = orderDishesNutrientsMapper.getByOrderId(orderDishesSnapshotDto.getOrderId());
        if (existsSnapShot != null) {
            int newVersion = existsSnapShot.getVersion().intValue() + 1;
            orderDishesNutrientsDO.setVersion((short) newVersion);
        }
        orderDishesNutrientsMapper.add(orderDishesNutrientsDO);
        LOGGER.info("function end parameter:{}.", orderDishesSnapshotDto);
    }

    @Override
    /**
     * 根据订单id获取订单快照数据
     * @param orderId
     * @return
     */
    public OrderNutrientsVO queryOrderNutrients(Long orderId) {
        OrderDishesNutrientsDO orderDishesNutrientsDO = orderDishesNutrientsMapper.getByOrderId(orderId);
        if (orderDishesNutrientsDO != null) {
            return TransformUtil.transform(orderDishesNutrientsDO);
        }
        return null;
    }

    /**
     * 获取菜品营养成分表列表
     *
     * @param dishesDOList
     * @return
     */
    private List<DishesNutrientsVO> getDishesNutrientsList(List<MerchantDishesDO> dishesDOList) {
        //获取菜品代码列表
        List<String> dishesCodes = new ArrayList<>();
        for (MerchantDishesDO dishesDO : dishesDOList) {
            dishesCodes.add(dishesDO.getCode());
        }
        List<DishesFoodIngredientsDO> dishesFoodIngredientsDOList = dishesFoodIngredientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistFoods = (dishesFoodIngredientsDOList == null || dishesFoodIngredientsDOList.isEmpty());
        List<DishesNutrientsDO> dishesNutrientsDOList = dishesNutrientsMapper.queryForListByDishesCodes(dishesCodes);
        Boolean notExistsNutrientsRecord = (dishesNutrientsDOList == null || dishesNutrientsDOList.isEmpty());
        //无食材列表、营养成分表直接返回
        if (notExistFoods && notExistsNutrientsRecord) {
            dishesDOList.stream().map(x -> TransformUtil.transform(x, (List<DishesFoodVO>) null, null)).collect(Collectors.toList());
        }
        //处理食材列表
        Map<String, List<DishesFoodVO>> dishesFoodVoListMap = getDishesFoodVoMap(dishesFoodIngredientsDOList, notExistFoods);
        //处理菜品营养元素列表
        Map<String, List<NutrientsVO>> dishesNutrientsVoListMap = getNutrientsVoMap(dishesNutrientsDOList, notExistsNutrientsRecord);
        return dishesDOList.stream().map(x -> TransformUtil.transform(x, dishesFoodVoListMap.get(x.getCode()), dishesNutrientsVoListMap.get(x.getCode()))).collect(Collectors.toList());
    }

    /**
     * 处理食材列表
     *
     * @param dishesNutrientsDOList
     * @param notExistsNutrientsRecord
     * @return
     */
    private Map<String, List<NutrientsVO>> getNutrientsVoMap(List<DishesNutrientsDO> dishesNutrientsDOList, Boolean notExistsNutrientsRecord) {
        Map<String, List<NutrientsVO>> dishesNutrientsVoListMap = new HashMap<>(16);
        if (!notExistsNutrientsRecord) {
            List<NutrientsVO> nutrientsVOList;
            for (DishesNutrientsDO dishesNutrientsDO : dishesNutrientsDOList) {
                if (dishesNutrientsVoListMap.containsKey(dishesNutrientsDO.getDishesCode())) {
                    nutrientsVOList = dishesNutrientsVoListMap.get(dishesNutrientsDO.getDishesCode());
                } else {
                    nutrientsVOList = new ArrayList<>();
                }
                nutrientsVOList.add(TransformUtil.transform(dishesNutrientsDO));
                dishesNutrientsVoListMap.put(dishesNutrientsDO.getDishesCode(), nutrientsVOList);
            }
        }
        return dishesNutrientsVoListMap;
    }

    /**
     * 处理菜品营养元素列表
     *
     * @param dishesFoodIngredientsDOList
     * @param notExistFoods
     * @return
     */
    private Map<String, List<DishesFoodVO>> getDishesFoodVoMap(List<DishesFoodIngredientsDO> dishesFoodIngredientsDOList, Boolean notExistFoods) {
        Map<String, List<DishesFoodVO>> dishesFoodVoListMap = new HashMap<>(16);
        if (!notExistFoods) {
            List<DishesFoodVO> dishesFoodVOList;
            for (DishesFoodIngredientsDO dishesFoodIngredientsDO : dishesFoodIngredientsDOList) {
                if (dishesFoodVoListMap.containsKey(dishesFoodIngredientsDO.getDishesCode())) {
                    dishesFoodVOList = dishesFoodVoListMap.get(dishesFoodIngredientsDO.getDishesCode());
                } else {
                    dishesFoodVOList = new ArrayList<>();
                }
                dishesFoodVOList.add(TransformUtil.transform(dishesFoodIngredientsDO));
                dishesFoodVoListMap.put(dishesFoodIngredientsDO.getDishesCode(), dishesFoodVOList);
            }
        }
        return dishesFoodVoListMap;
    }

    /**
     * 根据菜品对象，返回菜品营养元素列表
     *
     * @param dishesDO
     * @return
     */
    private DishesNutrientsVO queryNutrientsOfDishesByDishes(MerchantDishesDO dishesDO) {
        Map<String, Object> queryFoodsParam = new HashMap<>(16);
        queryFoodsParam.put("dishes_code", dishesDO.getCode());
        queryFoodsParam.put("flag", 1);
        //查询关联的食材关系数据列表
        List<DishesFoodIngredientsDO> dishesFoodIngredientsDOList = dishesFoodIngredientsMapper.queryForList(queryFoodsParam);
        Boolean existFoods = (dishesFoodIngredientsDOList == null || dishesFoodIngredientsDOList.isEmpty());
        //查询菜品营养成分表
        Map<String, Object> dishesNutrientsQueryMap = new HashMap<>(16);
        dishesNutrientsQueryMap.put("flag", 1);
        dishesNutrientsQueryMap.put("dishes_code", dishesDO.getCode());
        List<DishesNutrientsDO> dishesNutrientsDOList = dishesNutrientsMapper.queryForList(dishesNutrientsQueryMap);
        Boolean existsNutrientsRecord = (dishesNutrientsDOList == null || dishesNutrientsDOList.isEmpty());
        if (existFoods && existsNutrientsRecord) {
            return TransformUtil.transform(dishesDO, (List<DishesFoodVO>) null, null);
        }
        //处理食材列表
        List<DishesFoodVO> dishesFoodVOList = null;
        if (!existFoods) {
            dishesFoodVOList = new ArrayList<>();
            for (DishesFoodIngredientsDO dishesFoodIngredientsDO : dishesFoodIngredientsDOList) {
                dishesFoodVOList.add(TransformUtil.transform(dishesFoodIngredientsDO));
            }
        }
        //处理菜品营养元素列表
        List<NutrientsVO> nutrientsVOList = null;
        if (!existsNutrientsRecord) {
            nutrientsVOList = new ArrayList<>();
            for (DishesNutrientsDO dishesNutrientsDO : dishesNutrientsDOList) {
                nutrientsVOList.add(TransformUtil.transform(dishesNutrientsDO));
            }
        }
        return TransformUtil.transform(dishesDO, dishesFoodVOList, nutrientsVOList);
    }

    private String generateMerchantDishesCode() {
        String codeStuff = null;
        Integer seq = null;
        synchronized (M_CODE_LOCK) {
            M_CODE_HOLD_COUNT++;
            codeStuff = String.format("%s", ConstantValues.CODE_FORMAT.format(new Date()));
            seq = commonMapper.querySeqByCode("t_nutrients_merchant_dishes", codeStuff) + M_CODE_HOLD_COUNT;
        }
        return String.format("%s%05d", codeStuff, seq);
    }

    private String generateDishesCode() {
        String codeStuff = null;
        Integer seq = null;
        synchronized (T_CODE_LOCK) {
            T_CODE_HOLD_COUNT++;
            codeStuff = String.format("T%s", ConstantValues.CODE_FORMAT.format(new Date()));
            seq = commonMapper.querySeqByCode("t_nutrients_dishes", codeStuff) + T_CODE_HOLD_COUNT;
        }
        return String.format("%s%05d", codeStuff, seq);
    }

}
